{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Additional Uses"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remember what I said in the last lecture about some common patterns we can implement with context managers:\n",
    "\n",
    "* Open - Close\n",
    "* Change - Reset\n",
    "* Start - Stop"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The open file context manager is an example of the **Open - Close** pattern. But we have other ones as well."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Decimal Contexts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Decimals have a context which can be used to define many things, such as precision, rounding mechanism, etc.\n",
    "\n",
    "By default, Decimals have a \"global\" context - i.e. one that will apply to any Decimal object by default:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import decimal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "decimal.getcontext()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we create a decimal object, then it will use those settings.\n",
    "\n",
    "We can certainly change the properties of that global context:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "decimal.getcontext().prec=14"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Context(prec=14, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "decimal.getcontext()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now the default (global) context has a precision set to 14.\n",
    "\n",
    "Let's reset it back to 28:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "decimal.getcontext().prec = 28"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Suppose now that we just want to temporarily change something in the context - we would have to do something like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.3333\n",
      "0.3333333333333333333333333333\n"
     ]
    }
   ],
   "source": [
    "old_prec = decimal.getcontext().prec\n",
    "decimal.getcontext().prec = 4\n",
    "print(decimal.Decimal(1) / decimal.Decimal(3))\n",
    "decimal.getcontext().prec = old_prec\n",
    "print(decimal.Decimal(1) / decimal.Decimal(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, this is kind of a pain to have to store the current value, set it to something new, and then remember to set it back to it's original value.\n",
    "\n",
    "How about writing a context manager to do that seamlessly for us:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class precision:\n",
    "    def __init__(self, prec):\n",
    "        self.prec = prec\n",
    "        self.current_prec = decimal.getcontext().prec\n",
    "        \n",
    "    def __enter__(self):\n",
    "        decimal.getcontext().prec = self.prec\n",
    "        \n",
    "    def __exit__(self, exc_type, exc_value, exc_traceback):\n",
    "        decimal.getcontext().prec = self.current_prec\n",
    "        return False      \n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can do this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.333\n",
      "0.3333333333333333333333333333\n"
     ]
    }
   ],
   "source": [
    "with precision(3):\n",
    "    print(decimal.Decimal(1) / decimal.Decimal(3))\n",
    "print(decimal.Decimal(1) / decimal.Decimal(3))    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And as you can see, the precision was set back to it's original value once the context was exited."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In fact, the decimal class already has a context manager, and it's way better than ours, because we can set not only the precision, but anything else we want:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.333\n",
      "0.3333333333333333333333333333\n"
     ]
    }
   ],
   "source": [
    "with decimal.localcontext() as ctx:\n",
    "    ctx.prec = 3\n",
    "    print(decimal.Decimal(1) / decimal.Decimal(3))\n",
    "print(decimal.Decimal(1) / decimal.Decimal(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So this is an example of using a context manager for a **Change - Reset** type of situation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Timing a with block"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's another example of a **Start - Stop** type of context manager.\n",
    "We'll create a context manager to time the code inside the `with` block:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from time import perf_counter, sleep\n",
    "\n",
    "class Timer:\n",
    "    def __init__(self):\n",
    "        self.elapsed = 0\n",
    "        \n",
    "    def __enter__(self):\n",
    "        self.start = perf_counter()\n",
    "        return self\n",
    "    \n",
    "    def __exit__(self, exc_type, exc_value, exc_traceback):\n",
    "        self.stop = perf_counter()\n",
    "        self.elapsed = self.stop - self.start\n",
    "        return False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You'll note that this time we are returning the context manager itself from the `__enter__` statement. This will allow us to look at the `elapsed` property of the context manager once the `with` statement has finished running."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.9993739623039163\n"
     ]
    }
   ],
   "source": [
    "with Timer() as timer:\n",
    "    sleep(1)\n",
    "print(timer.elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "#### Redirecting stdout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we are going to temporarily redirect `stdout` to a file instead fo the console:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "class OutToFile:\n",
    "    def __init__(self, fname):\n",
    "        self._fname = fname\n",
    "        self._current_stdout = sys.stdout\n",
    "        \n",
    "    def __enter__(self):\n",
    "        self._file = open(self._fname, 'w')\n",
    "        sys.stdout = self._file\n",
    "        \n",
    "    def __exit__(self, exc_type, exc_value, exc_tb):\n",
    "        sys.stdout = self._current_stdout\n",
    "        if self._file:\n",
    "            self._file.close()\n",
    "        return False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "with OutToFile('test.txt'):\n",
    "    print('Line 1')\n",
    "    print('Line 2')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, no output happened on the console... Instead the output went to the file we specified. And our print statements will now output to the console again:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "back to console output\n"
     ]
    }
   ],
   "source": [
    "print('back to console output')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Line 1\\n', 'Line 2\\n']\n"
     ]
    }
   ],
   "source": [
    "with open('test.txt') as f:\n",
    "    print(f.readlines())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### HTML Tags"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this example, we're going to basically use a context manager to inject opening and closing html tags as we print to the console (of course we could redirect our prints somewhere else as we just saw!):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Tag:\n",
    "    def __init__(self, tag):\n",
    "        self._tag = tag\n",
    "        \n",
    "    def __enter__(self):\n",
    "        print(f'<{self._tag}>', end='')\n",
    "        \n",
    "    def __exit__(self, exc_type, exc_value, exc_tb):\n",
    "        print(f'</{self._tag}>', end='')\n",
    "        return False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<p>some <b>bold</b> text</p>"
     ]
    }
   ],
   "source": [
    "with Tag('p'):\n",
    "    print('some ', end='')\n",
    "    with Tag('b'):\n",
    "        print('bold', end='')\n",
    "    print(' text', end='')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Re-entrant Context Managers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also write context managers that can be re-entered in the sense that we can call `__enter__` and `__exit__` more than once on the **same** context manager. \n",
    "\n",
    "These methods are called when a `with` statement is used, so we'll need to be able to get our hands on the context manager object itself - but that's easy, we just return `self` from the `__enter__` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's write a ListMaker context manager to do see how this works."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class ListMaker:\n",
    "    def __init__(self, title, prefix='- ', indent=3):\n",
    "        self._title = title\n",
    "        self._prefix = prefix\n",
    "        self._indent = indent\n",
    "        self._current_indent = 0\n",
    "        print(title)\n",
    "        \n",
    "    def __enter__(self):\n",
    "        self._current_indent += self._indent\n",
    "        return self\n",
    "    \n",
    "    def __exit__(self, exc_type, exc_value, exc_tb):\n",
    "        self._current_indent -= self._indent\n",
    "        return False\n",
    "        \n",
    "    def print(self, arg):\n",
    "        s = ' ' * self._current_indent + self._prefix + str(arg)\n",
    "        print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because `__enter__` is returning `self`, the instance of the context manager, we can call `with` on that context manager and it will automatically call the `__enter__` and `__exit__` methods. Each time we run `__enter__` we increase the indentation, each time we run `__exit__` we decrease the indentation.\n",
    "\n",
    "Our `print` method then takes that into account when it prints the requested string argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Items\n",
      "   - Item 1\n",
      "      - item 1a\n",
      "      - item 1b\n",
      "   - Item 2\n",
      "      - item 2a\n",
      "      - item 2b\n"
     ]
    }
   ],
   "source": [
    "with ListMaker('Items') as lm:\n",
    "    lm.print('Item 1')\n",
    "    with lm:\n",
    "        lm.print('item 1a')\n",
    "        lm.print('item 1b')\n",
    "    lm.print('Item 2')\n",
    "    with lm:\n",
    "        lm.print('item 2a')\n",
    "        lm.print('item 2b')\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, we can easily redirect the output to a file instead, using the context manager we wrote earlier:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "with OutToFile('my_list.txt'):\n",
    "    with ListMaker('Items') as lm:\n",
    "        lm.print('Item 1')\n",
    "        with lm:\n",
    "            lm.print('item 1a')\n",
    "            lm.print('item 1b')\n",
    "        lm.print('Item 2')\n",
    "        with lm:\n",
    "            lm.print('item 2a')\n",
    "            lm.print('item 2b')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Items\n",
      "   - Item 1\n",
      "      - item 1a\n",
      "      - item 1b\n",
      "   - Item 2\n",
      "      - item 2a\n",
      "      - item 2b\n"
     ]
    }
   ],
   "source": [
    "with open('my_list.txt') as f:\n",
    "    for row in f:\n",
    "        print(row, end='')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
